package com.tencent.sr.iris.activity.common.sentinel;

import cn.hutool.core.util.StrUtil;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.google.common.collect.Maps;
import com.tencent.sr.iris.activity.common.config.ThirdReqSentinelSwitch;
import com.tencent.sr.rmall.common.exception.retail.TRetailErrorException;
import com.tencent.sr.rmall.springbootstarter.monitor.util.MonitorUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;
import java.util.Map;
import java.util.Optional;

@Slf4j
//@Aspect
//@Component
public class SentinelFeignAspect {

    @Resource
    private ThirdReqSentinelSwitch thirdReqSentinelSwitch;

    @Around("@within(feignClient) && @annotation(mapping)")
    public Object around(ProceedingJoinPoint pjp, FeignClient feignClient, RequestMapping mapping) throws Throwable {
        String[] value = mapping.value();
        if (value == null || value.length == 0) {
            return pjp.proceed();
        }
        String url = value[0];
        return doAround(pjp, url);
    }

    @Around("@within(feignClient) && @annotation(mapping)")
    public Object around(ProceedingJoinPoint pjp, FeignClient feignClient, PostMapping mapping) throws Throwable {
        String[] value = mapping.value();
        if (value == null || value.length == 0) {
            return pjp.proceed();
        }
        String url = value[0];
        return doAround(pjp, url);
    }

    private Object doAround(ProceedingJoinPoint pjp, String url) throws Throwable {
        if (!thirdReqSentinelSwitch.isFeignSwitchOn()) {
            return pjp.proceed();
        }

        Map<String, String> resourceNameMap = Optional.ofNullable(thirdReqSentinelSwitch.getResourceNameMap())
                .orElseGet(Maps::newHashMap);
        String resource = resourceNameMap.get(url);
        if (StrUtil.isBlank(resource)) {
            return pjp.proceed();
        }

        Entry entry = null;
        Throwable throwable = null;
        long startTime = System.currentTimeMillis();
        try {
            // 资源名可使用任意有业务语义的字符串，注意数目不能太多（超过 1K），超出几千请作为参数传入而不要直接作为资源名
            entry = SphU.entry(resource);
            return pjp.proceed();
        } catch (BlockException e) {
            throwable = e;
            log.error("feign调用触发限流，url：{}，resource：{}", url, resource);
            throw new TRetailErrorException(String.format("feign调用触发限流，url：%s，resource：%s", url, resource), e);
        } catch (Throwable e) {
            throwable = e;
            throw new TRetailErrorException(String.format("feign调用触发限流，url：%s，resource：%s", url, resource), e);
        } finally {
            // 务必保证 exit，务必保证每个 entry 与 exit 配对
            if (entry != null) {
                entry.exit();
            }
            MonitorUtil.getSingleMonitorService().logTransaction("feign sentinelLimit", resource,
                    startTime, throwable);
        }
    }
}